﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;

namespace Grebok.Persistence
{
    using Grebok.Domain;
    using Grebok.Persistence.QueryModel;

    public abstract class BaseDataContext : IDataContext, IDisposable
    {
        private bool isInTransaction = false;
        private bool isDirty = false;
        private List<ScheduledAction> actions = new List<ScheduledAction>();
        private IdentityMap identityMap = new IdentityMap();
        protected abstract IDataMapper<T> GetDataMapper<T>();
        protected abstract void ExecuteScheduledAction(ScheduledAction action);

        #region IObjectSpaceServices Members

        /// <summary>
        /// Reports whether this <c>ObjectSpaceServices</c> contain any changes which must be synchronized with the database
        /// </summary>
        public bool IsDirty
        {
            get { return isDirty; }
            private set { isDirty = value; }
        }

        /// <summary>
        /// Reports whether this <c>ObjectSpaceServices</c> is working transactionally
        /// </summary>
        public bool IsInTransaction
        {
            get { return isInTransaction; }
            private set { isInTransaction = value; }
        }

        public T GetById<T>(object key) where T : class, new()
        {
            T item = (T)identityMap.GetObject(typeof(T), key);
            if (item == null)
            {
                item = GetDataMapper<T>().GetByKey(key);
                identityMap.PutObject(item, key);
            }
            return item;
        }

        public IList<T> GetAll<T>() where T : class, new()
        {
            return GetDataMapper<T>().GetAll();
        }

        public IList<T> GetAll<T>(int pageIndex, int pageSize) where T : class, new()
        {
            throw new NotImplementedException("The method or operation is not implemented.");
        }

        public IList<T> GetByCriteria<T>(Query query) where T : class, new()
        {
            return GetDataMapper<T>().GetByCriteria(query);
        }

        public IList<T> GetByCriteria<T>(Query query, int pageIndex, int pageSize) where T : class, new()
        {
            throw new NotImplementedException("The method or operation is not implemented.");
        }

        public virtual int GetCount<T>()
        {
            throw new NotImplementedException();
        }

        public virtual int GetCount<T>(Query query)
        {
            throw new NotImplementedException();
        }

        private void ManageAction(ScheduledAction action)
        {
            if (this.IsInTransaction)
            {
                actions.Add(action);
            }
            else
            {
                this.ExecuteScheduledAction(action);
            }
        }

        public void Add(object item)
        {
            if (item == null)
            {
                throw new ArgumentNullException();
            }
            ScheduledAction action = new ScheduledAction(item, ScheduledAction.ActionType.Create);
            ManageAction(action);
        }

        public void Save(object item)
        {
            if (item == null)
            {
                throw new ArgumentNullException();
            }
            ScheduledAction action = new ScheduledAction(item, ScheduledAction.ActionType.Update);
            ManageAction(action);
        }

        public void Delete(object item)
        {
            if (item == null)
            {
                throw new ArgumentNullException();
            }
            ScheduledAction action = new ScheduledAction(item, ScheduledAction.ActionType.Delete);
            ManageAction(action);
        }

        /// <summary>
        /// Begins a transaction
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if there is an already active transaction</exception>
        public void BeginTransaction()
        {
            if (this.IsInTransaction)
            {
                throw new InvalidOperationException("A transaction is already opened");
            }
            else
            {
                this.IsInTransaction = true;
            }

        }

        /// <summary>
        /// Commits the active transaction
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if there isn't an active transaction</exception>
        public void Commit()
        {
            if (!this.IsInTransaction)
            {
                throw new InvalidOperationException("Operation requires an opened transaction");
            }
            else
            {
                foreach (ScheduledAction action in actions)
                {
                    DomainObject target = action.Target as DomainObject;
                    if (target != null && !target.IsValid)      //TODO: Enhance this
                    {
                        throw new ApplicationException(string.Format("Validation failed for object type: {0}", target.GetType().Name));
                    }
                }

                using (TransactionScope tx = new TransactionScope())
                {
                    actions.ForEach(delegate(ScheduledAction action) { this.ExecuteScheduledAction(action); });
                    tx.Complete();
                }
                actions.Clear();
                this.IsInTransaction = false;
            }
        }

        /// <summary>
        /// Rollbacks the active transaction
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if there isn't an active transaction</exception>
        public void Rollback()
        {
            if (!this.IsInTransaction)
            {
                throw new InvalidOperationException("Operation requires an opened transaction");
            }
            else
            {
                this.IsInTransaction = false;
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (this.IsInTransaction)
            {
                this.Rollback();
            }
        }

        #endregion
    }
}
